﻿const Const = require("./Const")
const ImProtocol = require("../ImCommon/Protocol")
const sqlite3 = require("sqlite3")

/**
 * 服务器类的构造方法。
 */
function Server() {
    const mDatabase = new sqlite3.Database(Const.DB_PATH)   // 数据库
    const mFriendTable = new Map()                          // 好友表
    const mOfflineAddFriendTable = new Map()                // 离线添加好友表
    const mOfflineChatTable = new Map()                     // 离线聊天表
    const mOnlineUsers = new Map()                          // 在线用户列表
    const mUserTable = new Map()                            // 用户表

    // 创建数据库
    createDatabase()

    // 创建服务
    var server = require("nodejs-websocket").createServer(onAccept)

    // 监听 IP 地址和端口
    server.listen(ImProtocol.SERVER_PORT, ImProtocol.SERVER_IP, function() {
        // 显示 IP 地址和端口号
        var address = server.socket._connectionKey.split(":")
        const ip = address[1]
        const port = address[2]
        console.log("IP: " + ip)
        console.log("Port: " + port)
    }) 

    /**
     * 创建数据库。
     */
    function createDatabase() {
        var sql 

        /* 用户表 */
        sql = "CREATE TABLE IF NOT EXISTS %s(%s TEXT PRIMARY KEY,%s TEXT,%s INTEGER)"
        sql = sql.replace("%s", Const.TABLE_USERS)
        sql = sql.replace("%s", Const.FIELD_NAME)
        sql = sql.replace("%s", Const.FIELD_PASSWORD)
        sql = sql.replace("%s", Const.FIELD_ICON_INDEX)
        mDatabase.run(sql)

        /* 好友表 */
        sql = "CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT)"
        sql = sql.replace("%s", Const.TABLE_FRIENDS)
        sql = sql.replace("%s", Const.FIELD_OWNER)
        sql = sql.replace("%s", Const.FIELD_FRIEND_NAME)
        mDatabase.run(sql)

        /* 离线聊天表 */
        sql = "CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT,%s TEXT,%s TEXT)"
        sql = sql.replace("%s", Const.TABLE_OFFLINE_CHAT)
        sql = sql.replace("%s", Const.FIELD_DST_USER)
        sql = sql.replace("%s", Const.FIELD_SRC_USER)
        sql = sql.replace("%s", Const.FIELD_CONTENT)
        sql = sql.replace("%s", Const.FIELD_TIME)
        mDatabase.run(sql)

        /* 离线添加好友表 */
        sql = "CREATE TABLE IF NOT EXISTS %s(%s TEXT,%s TEXT,%s INTEGER)"
        sql = sql.replace("%s", Const.TABLE_OFFLINE_ADD_FRIEND)
        sql = sql.replace("%s", Const.FIELD_SRC_USER)
        sql = sql.replace("%s", Const.FIELD_DST_USER)
        sql = sql.replace("%s", Const.FIELD_RESULT)
        mDatabase.run(sql)

        // 查询所有表中数据并保存到 map 中
        /* 用户表 */
        sql = "SELECT * FROM " + Const.TABLE_USERS
        mDatabase.all(sql, readUserTable)

        /* 好友表 */
        sql = "SELECT * FROM " + Const.TABLE_FRIENDS
        mDatabase.all(sql, readFriendTable)

        /* 离线聊天表 */
        sql = "SELECT * FROM " + Const.TABLE_OFFLINE_CHAT
        mDatabase.all(sql, readOfflineChatTable)

        /* 离线添加好友表 */
        sql = "SELECT * FROM " + Const.TABLE_OFFLINE_ADD_FRIEND
        mDatabase.all(sql, readOfflineAddFriendTable)
    }

    /**
     * 处理添加好友请求。
     * @param request   添加好友请求
     * @param client    客户端
     */
    function handleAddFriendRequest(request, client) {
        var result = null

        // 检查目标用户是否存在
        const onlineUser = mUserTable.get(request.mDstUsername)
        if (onlineUser == null) {
            // 查无此人，通知源用户
            result = new ImProtocol.AddFriendResult(0, request.mDstUsername, ImProtocol.ResultCode.USER_NOT_FOUND, "")
            sendData(client, result)
            return
        }

        // 检查是否已经是好友了
        const friendList = mFriendTable.get(request.mDstUsername)
        if (friendList != null && friendList.indexOf(request.mSrcUsername) != -1) {
            // 已经是好友了，通知源用户
            result = new ImProtocol.AddFriendResult(0, request.mDstUsername, ImProtocol.ResultCode.ALREADY_BE_FRIENDS, "")
            sendData(client, result)
            return
        }

        // 如果目标用户不在线，则将请求信息插入到数据库中
        const friend = mOnlineUsers.get(request.mDstUsername)
        if (friend == null) {
            var offlineAddFriendRequestList = mOfflineAddFriendTable.get(request.mDstUsername)
            if (offlineAddFriendRequestList == null) {
                offlineAddFriendRequestList = new Array()
                mOfflineAddFriendTable.set(request.mDstUsername, offlineAddFriendRequestList)
            }
            offlineAddFriendRequestList.push(request)
            var sql = "INSERT INTO %s(%s,%s) VALUES(\"%s\",\"%s\")"
            sql = sql.replace("%s", Const.TABLE_OFFLINE_ADD_FRIEND)
            sql = sql.replace("%s", Const.FIELD_SRC_USER)
            sql = sql.replace("%s", Const.FIELD_DST_USER)
            sql = sql.replace("%s", request.mSrcUsername)
            sql = sql.replace("%s", request.mDstUsername)
            mDatabase.run(sql)
            return
        }

        // 向目标用户发送请求
        sendData(friend, request)
    }

    /**
     * 处理添加好友结果。
     * @param result    添加好友结果
     */
    function handleAddFriendResult(result) {
        // 如果结果是同意，则将相关信息写到好友表中
        if (result.mResultCode == ImProtocol.ResultCode.ADD_FRIEND_ALLOW) {
            var friendList1 = mFriendTable.get(result.mSrcUsername)
            if (friendList1 == null) {
                friendList1 = new Array()
                mFriendTable.set(result.mSrcUsername, friendList1)
            }
            friendList1.push(result.mDstUsername)

            var friendList2 = mFriendTable.get(result.mDstUsername)
            if (friendList2 == null) {
                friendList2 = new Array()
                mFriendTable.set(result.mDstUsername, friendList2)
            }
            friendList2.push(result.mSrcUsername)
            var sql = "INSERT INTO %s VALUES(\"%s\",\"%s\")"
            sql = sql.replace("%s", Const.TABLE_FRIENDS)
            sql = sql.replace("%s", result.mSrcUsername)
            sql = sql.replace("%s", result.mDstUsername)
            mDatabase.run(sql)
            sql = "INSERT INTO %s VALUES(\"%s\",\"%s\")"
            sql = sql.replace("%s", Const.TABLE_FRIENDS)
            sql = sql.replace("%s", result.mDstUsername)
            sql = sql.replace("%s", result.mSrcUsername)
            mDatabase.run(sql)
        }

        // 如果目标用户不在线，则将结果信息插入到数据库中
        const friend = mOnlineUsers.get(result.mSrcUsername)
        if (friend == null) {
            var offlineAddFriendResultList = mOfflineAddFriendTable.get(result.mSrcUsername)
            if (offlineAddFriendResultList == null) {
                offlineAddFriendResultList = new Array()
                mOfflineAddFriendTable.set(result.mSrcUsername, offlineAddFriendResultList)
            }
            result.mDstIconIndex = -1
            offlineAddFriendResultList.push(result)

            var sql = "INSERT INTO %s VALUES(\"%s\",\"%s\",\"%s\")"
            sql = sql.replace("%s", Const.TABLE_OFFLINE_ADD_FRIEND)
            sql = sql.replace("%s", result.mSrcUsername)
            sql = sql.replace("%s", result.mDstUsername)
            sql = sql.replace("%s", result.mResultCode)
            mDatabase.run(sql)
            return
        }

        // 向目标用户发送结果
        sendData(friend, result)
    }

    /**
     * 处理聊天。
     * @param chatInfo  聊天信息
     */
    function handleChat(chatInfo) {
        // 处理群发消息
        if (chatInfo.mDstUsername == ImProtocol.MULTI_CHAT) {
            mOnlineUsers.forEach(function(value, key) {
                // 跳过自己，向其它用户发送聊天信息
                if (key != chatInfo.mSrcUsername) {
                    sendData(value, chatInfo)
                }
            })
            return
        }

        // 如果目标用户不在线，则将聊天信息插入到数据库中
        const friend = mOnlineUsers.get(chatInfo.mDstUsername)
        if (friend == null) {
            var offlineChatList = mOfflineChatTable.get(chatInfo.mDstUsername)
            if (offlineChatList == null) {
                offlineChatList = new Array()
                mOfflineChatTable.set(chatInfo.mDstUsername, offlineChatList)
            }
            offlineChatList.push(chatInfo)
            var sql = "INSERT INTO %s VALUES(\"%s\",\"%s\",\"%s\",\"%s\")"
            sql = sql.replace("%s", Const.TABLE_OFFLINE_CHAT)
            sql = sql.replace("%s", chatInfo.mDstUsername)
            sql = sql.replace("%s", chatInfo.mSrcUsername)
            sql = sql.replace("%s", chatInfo.mContent)
            sql = sql.replace("%s", chatInfo.mTime)
            mDatabase.run(sql)
            return
        }

        // 发送给目标用户
        sendData(friend, chatInfo)
    }

    /**
     * 处理离线请求。
     * @param request   离线请求
     */
    function handleLogoff(request) {
        // 将该用户从在线列表移除
        mOnlineUsers.delete(request.mUsername)

        // 广播该用户的离线请求
        mOnlineUsers.forEach(function(value, key) {
            sendData(value, request)
        }) 
    }

    /**
     * 处理登录请求。
     * @param request   登录请求
     * @param client    客户端
     */
    function handleLogon(request, client) {
        const logonResult = new ImProtocol.LogonResult()
        var sql = null

        // 检查该用户是否已经登录
        var onlineUser = mOnlineUsers.get(request.mUserInfo.mUsername)
        if (onlineUser != null) {
            // 已经登录了
            logonResult.mResultCode = ImProtocol.ResultCode.USER_ALREADY_LOGON
            sendData(client, logonResult)
            return 
        }

        // 从数据库中查找该用户
        const user = mUserTable.get(request.mUserInfo.mUsername)
        if (user == null) {
            // 查无此人
            logonResult.mResultCode = ImProtocol.ResultCode.USER_NOT_FOUND
            sendData(client, logonResult)
            return
        }

        // 比对密码
        if (request.mUserInfo.mPassword != user.Password) {
            // 密码不正确
            logonResult.mResultCode = ImProtocol.ResultCode.PASSWORD_WRONG
            sendData(client, logonResult)
            return
        }

        // 登录成功，获取该用户的相关信息
        logonResult.mResultCode = ImProtocol.ResultCode.LOGON_SUCCEEDED
        logonResult.mIconIndex = user.IconIndex

        // 获取该用户的好友列表
        if (mFriendTable.size > 0) {
            logonResult.mfriendNames = mFriendTable.get(request.mUserInfo.mUsername)
        }
        if (logonResult.mfriendNames != null) {
            const friendCount = logonResult.mfriendNames.length
            logonResult.mfriendIconIndexs = new Array(friendCount)
            logonResult.mFriendsOnline = new Array(friendCount)
            for (var i = 0; i < friendCount; i++) {
                const friendInfo = mUserTable.get(logonResult.mfriendNames[i])
                logonResult.mfriendIconIndexs[i] = friendInfo.IconIndex
                const friendUser = mOnlineUsers.get(logonResult.mfriendNames[i])
                const isOnline = (friendUser != null)
                logonResult.mFriendsOnline[i] = isOnline

                // 如果好友在线，则向好友发送通知
                if (isOnline) {
                    const friendLogon = new ImProtocol.FriendLogon(request.mUserInfo.mUsername)
                    sendData(friendUser, friendLogon)
                }
            }
            logonResult.mFriendCount = friendCount
        }

        // 发送登录结果
        sendData(client, logonResult)

        // 查询是否有和该用户相关的离线添加好友请求，有则发送
        const offlineAddFriendRequestList = mOfflineAddFriendTable.get(request.mUserInfo.mUsername)
        if (offlineAddFriendRequestList != null) {
            for (var i = 0; i < offlineAddFriendRequestList.length; i++) {
                sendData(client, offlineAddFriendRequestList[i])
                offlineAddFriendRequestList.splice(i, 1)
            }

            // 删除列表中的离线添加好友请求
            mOfflineAddFriendTable.delete(request.mUserInfo.mUsername)

            // 删除数据库中的离线添加好友请求
            sql = "DELETE FROM %s WHERE %s=\"%s\""
            sql = sql.replace("%s", Const.TABLE_OFFLINE_ADD_FRIEND)
            sql = sql.replace("%s", Const.FIELD_DST_USER)
            sql = sql.replace("%s", request.mUserInfo.mUsername)
            mDatabase.run(sql)
        }

        // 查询是否有和该用户相关的离线添加好友结果，有则发送
        const offlineAddFriendResultList = mOfflineAddFriendTable.get(request.mUserInfo.mUsername)
        if (offlineAddFriendResultList != null) {
            for (var i = 0; i < offlineAddFriendResultList.length; i++) {
                offlineAddFriendResultList[i].mIconIndex = -1   // 避免重复添加好友
                sendData(client, offlineAddFriendResultList[i])
                offlineAddFriendResultList.splice(i, 1)
            }

            // 删除列表中的离线添加好友结果
            mOfflineAddFriendTable.delete(request.mUserInfo.mUsername)

            // 删除数据库中的离线添加好友结果
            sql = "DELETE FROM %s WHERE %s=\"%s\""
            sql = sql.replace("%s", Const.TABLE_OFFLINE_ADD_FRIEND)
            sql = sql.replace("%s", Const.FIELD_SRC_USER)
            sql = sql.replace("%s", request.mUserInfo.mUsername)
            mDatabase.run(sql)
        }

        // 查询是否有和该用户相关的离线聊天消息，有则发送
        const offlineChatList = mOfflineChatTable.get(request.mUserInfo.mUsername)
        if (offlineChatList != null) {
            for (var i = 0; i < offlineChatList.length; i++) {
                sendData(client, offlineChatList[i])
            }

            // 删除列表中的离线聊天信息
            mOfflineChatTable.delete(request.mUserInfo.mUsername)

            // 删除数据库中的离线聊天信息
            sql = "DELETE FROM %s WHERE %s=\"%s\""
            sql = sql.replace("%s", Const.TABLE_OFFLINE_CHAT)
            sql = sql.replace("%s", Const.FIELD_DST_USER)
            sql = sql.replace("%s", request.mUserInfo.mUsername)
            mDatabase.run(sql)
        }

        // 将该用户加入在线列表
        mOnlineUsers.set(request.mUserInfo.mUsername, client)
    }

    /**
     * 处理注册请求。
     * @param reques    注册请求
     * @param client    客户端
     */
    function handleReg(request, client) {
        const regResult = new ImProtocol.RegResult()

        // 检查该用户是否已存在
        var user = mUserTable.get(request.mUserInfo.mUsername)
        if (user != null) {
            // 该用户已存在
            regResult.mResultCode = ImProtocol.ResultCode.USER_EXISTS
            sendData(client, regResult)
            return
        }

        // 将该用户信息插入到数据库中
        eval("user = {" + Const.FIELD_NAME + ":\"" + request.mUserInfo.mUsername + "\"," +
            Const.FIELD_PASSWORD + ":\"" + request.mUserInfo.mPassword + "\"," +
            Const.FIELD_ICON_INDEX + ":\"" + request.mUserInfo.mIconIndex + "\"}")
        var sql = "INSERT INTO %s VALUES(\"%s\",\"%s\",\"%s\")"
        sql = sql.replace("%s", Const.TABLE_USERS)
        sql = sql.replace("%s", request.mUserInfo.mUsername)
        sql = sql.replace("%s", request.mUserInfo.mPassword)
        sql = sql.replace("%s", request.mUserInfo.mIconIndex)
        mDatabase.run(sql)
        
        // 将该用户信息插入到列表中
        mUserTable.set(request.mUserInfo.mUsername, user)

        // 发送结果
        regResult.mResultCode = ImProtocol.ResultCode.REG_SUCCEEDED
        sendData(client, regResult)

        // 将该用户加入到在线列表中
        mOnlineUsers.set(request.mUserInfo.mUsername, client)
    }

    /**
     * 客户端连接服务器事件的响应方法。
     */
    function onAccept(client) {
        /**
         * 接收到数据的响应方法。
         * @param data  数据
         */
        function onReceive(data) {
            // 将数据转换成协议
            const pro = JSON.parse(data)

            // 处理协议
            switch (pro.mType) {
                case ImProtocol.ProtocolType.ADD_FRIEND_REQUEST:
                    handleAddFriendRequest(pro, client)
                    break 
                case ImProtocol.ProtocolType.ADD_FRIEND_RESULT:
                    handleAddFriendResult(pro)
                    break 
                case ImProtocol.ProtocolType.CHAT:
                    handleChat(pro)
                    break 
                case ImProtocol.ProtocolType.LOGOFF:
                    handleLogoff(pro)
                    break 
                case ImProtocol.ProtocolType.LOGON:
                    handleLogon(pro, client)
                    break 
                case ImProtocol.ProtocolType.REG:
                    handleReg(pro, client)
                    break 
            }
        }

        client.on("text", onReceive)
        client.on("error", function (){})
    }


    /**
     * 读取好友表。
     * @param error     错误信息
     * @param result    结果
     */
    function readFriendTable(error, result) {
        if (result == null) {
            return
        }
        for (var i = 0; i < result.length; i++) {
            var friendList = mFriendTable.get(result[i].Owner)
            if (friendList == null) {
                friendList = new Array()
                mFriendTable.set(result[i].Owner, friendList)
            }
            friendList.push(result[i].FriendName)
        }
    }

    /**
     * 读取离线添加好友表。
     * @param error     错误信息
     * @param result    结果
     */
    function readOfflineAddFriendTable(error, result) {
        if (result == null) {
            return
        }
        for (var i = 0; i < result.length; i++) {
            var offlineAddFriendList = mOfflineAddFriendTable.get(result[i].DstUser)
            if (offlineAddFriendList == null) {
                offlineAddFriendList = new Array()
                mOfflineAddFriendTable.set(result[i].DstUser, offlineAddFriendList)
            }
            offlineAddFriendList.push(result[i])
        }
    }

    /**
     * 读取离线聊天表。
     * @param error     错误信息
     * @param result    结果
     */
    function readOfflineChatTable(error, result) {
        if (result == null) {
            return
        }
        for (var i = 0; i < result.length; i++) {
            var offlineChatList = mOfflineChatTable.get(result[i].DstUser)
            if (offlineChatList == null) {
                offlineChatList = new Array()
                mOfflineChatTable.set(result[i].DstUser, offlineChatList)
            }
            offlineChatList.push(result[i])
        }
    }

    /**
     * 读取用户表。
     * @param error     错误信息
     * @param result    结果
     */
    function readUserTable(error, result) {
        if (result == null) {
            return
        }
        for (var i = 0; i < result.length; i++) {
            mUserTable.set(result[i].Name, result[i])
        }
    }

    /**
     * 发送数据。
     * @param client    客户端
     * @param pro       协议
     */
    function sendData(client, pro) {
        var text = JSON.stringify(pro)
        client.sendText(text)
    }
}

// 创建服务
new Server()
